/**
 * ConnectionThread.java
 * 2009-4-22 上午08:46:16
 *
 */
package com.esen.auwserv;

import java.io.BufferedReader;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

/**
 * @author 何江舟
 *
 * HTTP连接线程类，每个线程对应一个HTTP连接。
 * 总体流程分为两个部分，即处理客户端请求和发送文件
 *              处理用户请求：
 *                      头部处理：创建一个默认的响应头部信息，即ResponseHeader对象，然后判断客户端请求的类型，根据判断结果设置响应头部相关信息
 *                      文件处理：提取请求中的文件名，判断请求的文件，如果请求的是一个目录，则默认文件为该目录下的index.html文件，如果文件不存在则生成错误信息页面发送给客户端；
 *                                                       判断文件格式，根据文件格式设置头部信息中的MIME类型；
 *              发送文件：
 *                      头部信息：根据修改后的ResponseHeader对象来设置响应头部信息
 *                      发送文件：通过Socket，以字节流的形式将文件发送给客户端
 * 附（HTTP请求格式，HTTP响应格式，以“www.esensoft.com/index.html为例”）：
 * HTTP请求：
 *      请 求 行：              GET /index.html HTTP/1.1
 *      服务器主机名：          Host: www.esensoft.com
 *      客户端系统信息：        User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
 *      浏览器信息：            Gecko/20050225 Firefox/1.0.1
 *      （空行）
 *      是否持久保持连接：      Connection: Keep-Alive
 * HTTP响应：
 *      响应状态：HTTP/1.1 200 OK
 *      响应日期：Date: Sat, 31 Dec 2005 23:59:59 GMT
 *      页面编码：Content-Type: text/html;charset=GB2312
 *      文件大小：Content-Length: 122
 *      （空行）
 *      文件内容：（通过Socket的输出流发送给客户端）  
 */
class ConnectionThread extends Thread {

  private Socket client; // 客户端套接字

  private int counter; // 连接数计数器

  /**
   * 由指定的客户端套接字和连接数计数器来创建一个ConnectionThread对象
   * 如：new ConnectionThread(client, 10);
   * 
   * @param client
   * @param counter
   */
  public ConnectionThread(Socket client, int counter) {
    this.client = client;
    this.counter = counter;
  }

  /**
   * 线程体，每个线程对应于一个用户请求
   */
  public void run() {
    try {
      String destIp = client.getInetAddress().toString(); // 客户端IP地址
      int destPort = client.getPort(); // 客户端端口号

      System.out.println("Connection " + counter + " : connected to " + destIp + " on port "
          + destPort + "."); // 在控制台打印与客户端的连接所使用的服务器系统端口，便于监控系统状态
      PrintStream outStream = new PrintStream(client.getOutputStream(), true); // 用PrintStream处理Socket的输出流
      BufferedReader inStream = new BufferedReader(new InputStreamReader(client.getInputStream())); // 用BufferedStream处理Socket的输入流，提高数据输出性能
      String request = inStream.readLine(); // 读取Web浏览器提交的请求信息
      String fileName = getFileName(request); // 获取客户端所请求的文件
      String fileType = getFileType(fileName); // 获得客户端所请求的文件的格式

      System.out.println("Received: " + request); // 在控制台打印出客户端HTTP请求行
      System.out.println("The file \"" + fileName + "\" requested."); // 在控制台打印出客户端请求的文件名
      System.out.println("Requested File Type: " + fileType); // 在控制台打印出客户端请求的文件格式

      File file = new File("webapps/" + fileName);
      ResponseHeader responseHeader = new ResponseHeader(); // 创建默认的响应头部信息

      if (file.exists()) { // 如果文件存在，则将文件发送给客户端
        int len = (int) file.length(); // 文件长度

        // 如果文件格式不为空，则根据文件格式设置响应头部信息的MIME类型，这个MIME类型是通过文件格式从ResponseHeader类中的MIME类型列表中获取的
        if (fileType != null) {
          responseHeader.setContentType((String) ResponseHeader.mimeType.get(fileType));
        }

        responseHeader.setContentLength(len); // 设置响应头部的内容长度，通知客户端要发送的文件的长度
        outStream.println(responseHeader.getHead()); // 将HTTP协议头部信息发送给客户端
        outStream.println();

        sendFile(outStream, file); // 发送客户端请求的文件
        outStream.flush(); // 清空缓存
      }
      else { // 如果文件不存在，则生成错误页面发送给客户端
        String noFound = "<html><head><title>Not Found</title></head><body><h1>Error 404——file not found </h1></body></html>";

        responseHeader.setStatus("HTTP/1.1 404 no Found");
        responseHeader.setContentLength(noFound.length() + 2);
        outStream.println(responseHeader.getHead());
        outStream.println();
        outStream.println(noFound);

        outStream.flush();
      }

      Thread.sleep(100); // 将线程暂挂100毫秒，防止因客户端请求过于的频繁而造成的服务器负载过大
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
    }
    catch (InterruptedException ie) { // 暂挂线程时可能出现的异常
      ie.printStackTrace();
    }
    finally { // 在finally块中关闭客户端套接字，保证该套接字能够关闭
      if (client != null) {
        try {
          client.close();
        }
        catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  /**
   * 获取要访问的文件名
   * 
   * @param request 客户端请求信息
   * @return 请求文件名
   */
  public String getFileName(String request) {
    String f = request.substring(request.indexOf(' ') + 1); // 去除请求头中紧接着GET的空格
    f = f.substring(0, f.indexOf(' '));

    try {
      if (f.charAt(0) == '/') { // 去除“GET”及其后的空格后，如果剩下的字符串是以'/'字符开头，则请求格式正确
        f = f.substring(1); // 去除客户端请求头信息中的'/'字符，剩下的就是请求的文件名
      }
    }
    catch (StringIndexOutOfBoundsException e) { // 如果出现字符串位置越界异常，则在控制台打印出异常信息，并发送一个错误页面给客户端
      e.printStackTrace();

      f = "error.html"; // 当服务器出现异常时，发送默认错误页面给客户端，此处为error.html
    }

    if ("".equals(f)) { // 如果用户没有指定文件名，则默认传送给用户的文件为“index.html”
      f = "index.html";
    }

    return f; // 将处理完客户端请求后的结果返回给客户端
  }

  /**
   * 将客户端请求的文件发送给客户端
   * 
   * @param outs 套接字输出流，用于将文件发送给客户端
   * @param file 客户端请求的文件，即要发送的文件
   */
  public void sendFile(PrintStream outs, File file) {
    BufferedInputStream in = null;
    try {
      in = new BufferedInputStream(new FileInputStream(file));

      int len = 10485760; // 每次从文件中读取的字节数
      int hasReaded = 0; // 实际读取的字节数
      byte[] buf = new byte[len];

      // 采用循环的方式发送文件
      while ((hasReaded = in.read(buf)) > 0) {
        outs.write(buf, 0, hasReaded); // 发送本次读取的文件字节
      }

      outs.flush(); // 清空缓存
    }
    catch (Exception e) { // 当发送文件出现异常时，在控制台打印异常信息，并以状态1推出程序，表示程序非正常退出
      e.printStackTrace();
      System.exit(0);
    }
    finally { // 由于文件发送已经完毕，需要关闭服务端读取要发送文件的流，在finally块中执行，能保证流的关闭
      if (in != null) { // 如果流不为空，则关闭
        try {
          in.close();
        }
        catch (IOException ioe) {
          ioe.printStackTrace();
        }
      }
    }
  }

  /**
   * 获得文件类型：
   *    首先获得文件名中最后一个'.'字符的位置，再通过String类的substring实例方法来获得这个'.'字符后的子串，即为客户端所请求的文件的格式
   * 
   * @param fileName
   * @return
   */
  public String getFileType(String fileName) {
    String fileType = null; // 默认文件格式为null值
    int lastPosition = fileName.lastIndexOf("."); // 获得文件名中最后一个'.'的位置，其后的子串就是文件格式

    if (lastPosition >= 0) { // 当lastPosition大于等于零时，说明输入的文件名包含文件格式
      fileType = fileName.substring(fileName.lastIndexOf(".") + 1);
    }

    return fileType;
  }
}
